<?php
/**
 * Created by PhpStorm.
 * User: edmmss
 * Date: 2018/12/6
 * Time: 18:08
 */

namespace Edmmss\Sentry\Yii2;

use yii\helpers\ArrayHelper;
use yii\helpers\Json;
use yii\log\Logger;
use yii\log\Target;
use Raven_Client;
use Yii;

class SentryTarget extends Target
{
    /**
     * 因为线上使用符号链接（软连接）进行部署，所以目录变化。同一个文件会识别成不同的分组
     * @var bool
     */
    public $useFileFingerPrint = false;

    /**
     * @var bool 发送到sentry的额外数据是否包含contextMessage($GLOBALS数组的相关信息)
     */
    public $includeContextMessage = true;

    /**
     * @var string sentry的dsn
     */
    public $dsn;

    /**
     * @var Raven_Client
     */
    public $sentryClient;

    /**
     * @var array sentry错误等级
     */
    public $sentryLevels = [
        Logger::LEVEL_ERROR         => Raven_Client::ERROR,
        Logger::LEVEL_WARNING       => Raven_Client::WARNING,
        Logger::LEVEL_INFO          => Raven_Client::INFO,
        Logger::LEVEL_TRACE         => Raven_Client::DEBUG,
        Logger::LEVEL_PROFILE       => Raven_Client::DEBUG,
        Logger::LEVEL_PROFILE_BEGIN => Raven_Client::DEBUG,
        Logger::LEVEL_PROFILE_END   => Raven_Client::DEBUG,
    ];

    /**
     * init
     *
     * @date   2018/12/7
     * @author edmmss
     */
    public function init()
    {
        parent::init(); // TODO: Change the autogenerated stub
        $this->sentryClient = new Raven_Client($this->dsn);
    }

    /**
     * 将错误日志发送sentry
     *
     * @date   2018/12/7
     * @author edmmss
     */
    public function export()
    {
        // TODO: Implement export() method.
        $this->messages = static::filterMessages($this->messages, $this->getLevels(), $this->categories, $this->except);
        foreach ($this->messages as $index => $message) {
            list($context, $level, $category, $timestamp) = $message;
            $data = $this->getMessageBaseData($level, $timestamp, $category);

            // 异常的。直接写就行了。没有其他参数影响的了
            if ($context instanceof \Throwable || $context instanceof \Exception) {
                if ($this->useFileFingerPrint) { // 是否使用自定的指纹进行分组
                    $tempFile = $context->getFile();
                    $tempLine = $context->getLine();
                    $tempCode = $context->getCode();
                    $tempTraceFunction = '';
                    $tempTraceClass = '';
                    $tempTraceType = '';
                    if ($context->getTrace()) {
                        $tempLastTrace = $context->getTrace()[0];
                        isset($tempLastTrace['function']) && $tempTraceFunction = $tempLastTrace['function'];
                        isset($tempLastTrace['class']) && $tempTraceClass = $tempLastTrace['class'];
                        isset($tempLastTrace['type']) && $tempTraceType = $tempLastTrace['type'];
                    }
                    $data['fingerprint'][] = 'group_' . md5(md5_file($tempFile) . $tempLine . $tempCode . $tempTraceFunction . $tempTraceClass . $tempTraceType);
                }
                $this->sentryClient->captureException($context, $data);
                continue;
            }

            if (is_string($context)) {
                $strMessage = $context;
            } elseif (is_array($context) && isset($context['_message']) && is_string($context['_message'])) {
                // 这个作为sentry的关键字
                $strMessage = $context['_message'];
                unset($context['_message']);

                $additional = ['extra', 'fingerprint', 'tags', 'user'];
                foreach ($additional as &$value) {
                    if (isset($context[$value])) {
                        $data[$value] = array_merge($data[$value], $context[$value]);
                    }
                }
                unset($value);
            } else { // 其他类型了
                $strMessage = $category;
                $data['extra']['fullMessage'] = $context;
            }

            $traces = isset($message[4]) ? $message[4] : true;

            if (is_array($traces)) {
                $data['extra']['traces'] = $traces;
            }

            if ($this->useFileFingerPrint && !$data['fingerprint'] && $traces) { // 是否使用自定的指纹进行分组
                $tempLastTrace = $traces[0];

                $tempTraceFile = $tempLastTrace['file'];
                $tempTraceLine = $tempLastTrace['line'];
                $tempTraceFunction = $tempLastTrace['function'];
                $tempTraceClass = $tempLastTrace['class'];
                $tempTraceType = $tempLastTrace['type'];

                $data['fingerprint'][] = 'group_' . md5(md5_file($tempTraceFile) . $tempTraceLine . $tempTraceFunction . $tempTraceClass . $tempTraceType);
            }

            $this->sentryClient->captureMessage($strMessage, [], $data, $traces, []);
        }
    }

    /**
     * 获取整理好的错误信息
     *
     * @date   2018/12/7
     * @author edmmss
     * @param $level
     * @param $timestamp
     * @param $category
     * @return array
     */
    public function getMessageBaseData($level, $timestamp, $category)
    {
        $data = [
            'level'       => $this->sentryLevels[$level],
            'timestamp'   => $timestamp,
            'extra'       => [],
            'fingerprint' => [],
            'tags'        => [
                'category' => $category,
            ],
            'user'        => [],
        ];

        if ($this->includeContextMessage) {
            $data['extra']['contextMessage'] = $this->getContextMessage();
        }

        return $data;
    }

    /**
     * 覆写，因为框架的方法发送到sentry格式不太好看
     *
     * @date   2018/12/7
     * @author edmmss
     * @return array|string
     */
    public function getContextMessage()
    {
        $context = ArrayHelper::filter($GLOBALS, $this->logVars);
        $result = [];
        foreach ($context as $key => $value) {
            $result["\${$key}"] = $value;
        }

        return $result;
    }
}
